package dungeonPackage;

import squareBorderPackage.*;

import java.util.*;

import be.kuleuven.cs.som.annotate.*;

/**
 * A class of CompositeDungeons involving maxCoordinates, squares and a motherDungeon.
 *
 * @param 	<E>
 * 			The type of squares the dungeon can contain.
 * 
 * @invar 	The map of subDungeons of this compositeDungeon must be a proper map of subDungeons.
 * 			|hasProperSubDungeons()
 * 
 * @version	1.0
 * @author Esther Alberts & Jessa Bekker
 */
public class CompositeDungeon<E extends Square> extends Dungeon<E> {

	/**
	 * Initialize a new CompositeDungeon with given max coordinates.
	 * 
	 * @effect	This new CompositeDungeon is initialized as a dungeon with the given maxCoordinates.
	 * 			|super(maxCoordinates)
	 */
	public CompositeDungeon(Orientation maxCoordinates) {
		super(maxCoordinates);
	}
	  
   /**
	* Terminate this dungeon.
	*
	* @post		This composite dungeon no longer references subdungeons.
	* 			| for all dungeons in getSubDungeons() : 
	* 			|	dungeon == null
	* @post		If this composite dungeon was not already terminated, the subdungeons
	* 			of this composite dungeon no longer refer to this composite dungeon a
	* 			its mother dungeon.
	* 			| if(! isTerminated())
	* 			|	then (! for some dungeon in (new getSubDungeons()) :
	* 			|			dungeon.getMotherDungeon() == this)
	*/
	@Override
	public void terminate() {
		if (!isTerminated()) {
			HashMap<Orientation, Dungeon<E>> formerSubDungeons = getSubDungeons();
			super.terminate();
			for(Orientation reference: formerSubDungeons.keySet())
				removeSubDungeonAt(reference);
		}
	}

	/**
	 * Check whether the x, y and z coordinates are equal in one of the subDungeons of the rootDungeon or in
	 * the rootDungeon itself.
	 * 
	 * @return	True if the x y and z coordinates are the same if the given orientation is 
	 * 			subtracted with the referenceOrientation of its dungeon in his motherDungeon.
	 */
	@Override
	protected boolean sameCoordinates(Orientation orientation){
		if(orientation.getX() == orientation.getY()
				&& orientation.getX() == orientation.getZ())
			return true;
		if(hasDirectSubDungeonAt(orientation)){
			if(getSubDungeonAt(orientation).sameCoordinates(orientation.subtract(getReferenceOfSubDungeonAt(orientation))))
				return true;
		}
		return false;
	}
	
	/**
	 * Remove the square at the given orientation
	 * 
	 * @post	If this composite dungeon has a subdungeon at the given orientation,
	 * 			there will be no square at the corresponding orientation of the given orientation
	 * 			in the subdungeon.
	 * 			| !(new getSubDungeonAt(reference)).hasSquareAt(orientation.subtract(reference))
	 */
	@Override
	public void removeSquareAt(Orientation orientation)
			throws IllegalArgumentException {
		super.removeSquareAt(orientation);
		
		if(hasDirectSubDungeonAt(orientation)) {
			Orientation reference = getReferenceOfSubDungeonAt(orientation); 
			getSubDungeonAt(reference).removeSquareAt(orientation.subtract(reference));
		}
	}

	/**
	 * Register a reference to the given square at the given orientation.
	 * 
	 * @effect	The given square will also be set in the subdungeon at the given orientation
	 * 			minus the reference orientation of the subdungeon at this orientation.
	 * 			| getSubDungeonAt(orientation).setWithoutModificatonSquareAt(
	 * 			|	orientation.substract(getReferenceOfSubDungeonAt(orientation));
	 * @throws	IllegalArgumentException
	 * 			There is no subdungeon at the given orientation.
	 * 			| !hasSubDungeonAt(orientation)
	 */
	@Override
	protected void setSquareInFieldsAt(Orientation orientation, E square) 
	throws IllegalArgumentException {
		if(!hasDirectSubDungeonAt(orientation))
			throw new IllegalArgumentException("No subdungeon at the given orientation!");
		super.setSquareInFieldsAt(orientation, square);
		Dungeon<E> subDungeon = getSubDungeonAt(orientation);
		Orientation reference = getReferenceOfSubDungeonAt(orientation);
		subDungeon.setSquareInFieldsAt(orientation.subtract(reference), square);
	}
	
	/**
	 * Get the subdungeon of this composite dungeon at the given orientation.
	 * 
	 * @param	orientation
	 * 			The orientation to get the subdungeon for.
	 * @return	...
	 * 			| result == getSubDungeons().get(getReferenceOrientationOfSubDungeonAt(orientation))
	 * @throws	IllegalArgumentException
	 * 			The given orientation must be valid
	 * 			| orientation == null
	 */
	public Dungeon<E> getSubDungeonAt(Orientation orientation) 
	throws IllegalArgumentException{
		if(orientation == null)
			throw new IllegalArgumentException("non effective orientation!");
		if(!hasDirectSubDungeonAt(orientation))
			throw new IllegalArgumentException("no subdungeon at the given orientation!");
			
		return getSubDungeons().get(getReferenceOfSubDungeonAt(orientation));
	}

	/**
	 * Get the map with the subdungeons of this square.
	 */
	@Basic
	public HashMap<Orientation, Dungeon<E>> getSubDungeons() {
		return new HashMap<Orientation, Dungeon<E>>(subDungeons);
	}
	
	
	/**
	 * Return the subdungeon which has as reference orientation the given orientation.
	 * 
	 * @param 	orientation
	 * 			The reference orientation of the subdungeon to get.
	 * @return	the subdungeon which has as reference orientation the given orientation.
	 * 			| result == getSubDungeons().get(orientation)
	 * @throws	IllegalArgumentException
	 * 			The given orientation must be effective
	 * 			| orientation == null
	 */
	public Dungeon<E> getSubDungeonWithReference(Orientation orientation) 
	throws IllegalArgumentException{
		if(orientation == null)
			throw new IllegalArgumentException("non effective orientation!");
		return getSubDungeons().get(orientation);
	}

	/**
	 * Get all SingularDungeons inserted in this compositeDungeon
	 */
	public HashSet<SingularDungeon<E>> getSingularDungeons(){
		HashSet<SingularDungeon<E>> singularDungeons = new HashSet<SingularDungeon<E>>();
		
		for(Orientation orientation : getSubDungeons().keySet()){
			 Dungeon<E> subDungeon = getSubDungeons().get(orientation);
			if(subDungeon instanceof SingularDungeon)
				 singularDungeons.add((SingularDungeon<E>) subDungeon);
			else
				singularDungeons.addAll(((CompositeDungeon<E>) subDungeon).getSingularDungeons()); 
		}
		
		return singularDungeons;
	}

	/**
	 * Return the reference orientation of the given dungeon.
	 * 
	 * @param 	dungeon
	 * 			The subdungeon to get the reference orientation for
	 * @return	The reference orientation of the given dungeon.
	 * 			| getSubDungeonAt(result) == dungeon
	 * @throws 	IllegalArgumentException
	 * 			This composite dungeon does not contain the given dungeon!
	 * 			| !hasAsSubDungeon(dungeon)
	 */
	public Orientation getReferenceOf(Dungeon<E> dungeon)
	throws IllegalArgumentException {
		if(!hasAsSubDungeon(dungeon))
			throw new IllegalArgumentException("This composite dungeon does not contain the given dungeon!");
		for(Orientation orientation : getSubDungeons().keySet()){
			if(getSubDungeonAt(orientation).equals(dungeon))
				return orientation;
			if(getSubDungeonAt(orientation) instanceof CompositeDungeon
					&& ((CompositeDungeon<E>) getSubDungeonAt(orientation)).hasAsSubDungeon(dungeon))
				return ((CompositeDungeon<E>) getSubDungeonAt(orientation)).getReferenceOf(dungeon).add(
						getReferenceOfSubDungeonAt(orientation));
		}
		return null;
	}
	
	/**
	 * Get the reference orientation of the subdungeon at the given orientation.
	 * 
	 * @param 	orientation
	 * 			An orientation of the subdungeon to get the reference orientation for
	 * @return	...
	 * 			| if for some reference of getSubDngeons().keySet() :
	 * 			|		overlapsWith(orientation, reference)
	 * 			|	then (result == reference)
	 */
	public Orientation getReferenceOfSubDungeonAt(Orientation orientation) {
		for(Orientation reference: getSubDungeons().keySet()){
			if(overlapsWith(orientation, reference ))
				return reference;
		}	
		return null;
	}
	
	/**
	 * Get the reference orientation of the singular dungeon at the given orientation.
	 * 
	 * @param 	orientation
	 * 			An orientation of the singular dungeon to get the reference orientation for
	 * @return	...
	 * 			| if (getSubDungeonAt(orientation) instanceof SingularDungeon)
	 * 			|	then (result == getReferenceOfDungeonAt(orientation)
	 * 			| else	
	 * 			|	result == getSubDungeonAt(orientation).getReferenceOfSingularAt(orientation)
	 */
	public Orientation getReferenceOfSingularAt(Orientation orientation){
		Dungeon<E> subDungeon = getSubDungeonAt(orientation); 
		Orientation reference = getReferenceOfSubDungeonAt(orientation);
		if(subDungeon instanceof SingularDungeon)
			return reference;
		
		Orientation subReference = ((CompositeDungeon<E>) subDungeon).getReferenceOfSingularAt(orientation.subtract(reference));
		return subReference.add(reference);
	}

	/**
	 * Check whether the subDungeon would be completely inside this composite dungeon
	 * 		if it were added at the given orientation.
	 * 	 
	 * @param 	reference
	 * 			The orientation where the given dungeon would be added.
	 * @param 	subDungeon
	 * 			The subdungeon to be added.
	 * @return	...
	 * 			| result 
	 * 			|	= for each orientation of subDungeon.keySet() :
	 * 			|		!orientation.add(reference).isOutside(getMaxCoordinates())
	 */
	public boolean isInsideDungeon(Orientation reference, Dungeon<E> subDungeon) {
		if(reference.isOutside(getMaxCoordinates()))
			return false;
		Orientation maximumMax = getMaxCoordinates().subtract(reference);
		return (!subDungeon.getMaxCoordinates().isOutside(maximumMax));
	}

	/**
	 * Check whether this dungeon has this subdungeon as a subdungeon.
	 * 	Subdungeons of subdungeons are also subdungeons of this dungeon. 
	 * 
	 * @param 	dungeon
	 * 			The dungeon to check whether it is a subdungeon of this dungeon.
	 * @return	True if and only if this dungeon is a direct subdungeon of this dungeon
	 * 			or if it is a subdungeon of one of its direct subdungeons.
	 * 			| if(hasAsDirectSubDungeon(dungeon))
	 *			|	then (result == true)
	 *			| else if for some orientation of getSubDungeons().keySet() :
	 *			|		getSubDungeonAt(orientation).hasAsSubDungeon(dungeon)
	 *			|	then (result == true)
	 *			| else
	 *			|	then (result == false)
	 */
	public boolean hasAsSubDungeon(Dungeon<E> dungeon){
		if(hasAsDirectSubDungeon(dungeon))
			return true;
		
		for(Orientation orientation : getSubDungeons().keySet()){
			if(!(getSubDungeonAt(orientation) instanceof SingularDungeon)){
				if(((CompositeDungeon<E>) getSubDungeonAt(orientation)).hasAsSubDungeon(dungeon))
					return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Check whether this composite dungeon has the given dungeon as a subdungeon.
	 * 
	 * @param 	dungeon
	 * 			The dungeon to check for.
	 * @return	True if and only if this composite dungeon has the given dungeon as a subdungeon.
	 * 			| result == getSubDungeons().containsValue(dungeon);
	 */
	public boolean hasAsDirectSubDungeon(Dungeon<E> dungeon) {
		return getSubDungeons().containsValue(dungeon);
	}

	/**
	 * Check whether this composite dungeon has a subdungeon at the given orientation
	 * 
	 * @param	orientation
	 * 			The orientation to check for a subdungeon
	 * @return	True if and only if this composite dungeon has a subdungeon at the given direction.
	 * 			| result == (getSubDungeonAt(orientation) != null)
	 */
	public boolean hasDirectSubDungeonAt(Orientation orientation) {
		return hasDirectSubDungeonWithReference(getReferenceOfSubDungeonAt(orientation));
	}

	/**
	 * Check whether this composite dungeon has a subdungeon with the given reference.
	 * 
	 * @param 	reference
	 * 			The reference to check for.
	 * @return	True if and only if this composite dungeon has a subdungeon with the given reference.
	 * 			| result == getSubDungeons().containsKey(reference)
	 */
	public boolean hasDirectSubDungeonWithReference(Orientation reference) {
		return getSubDungeons().containsKey(reference);
	}

	/**
	 * Check whether the subdungeon with the given reference overlaps the given orientation.
	 * 
	 * @param 	orientation
	 * 			The orientation to check to be overlapped.
	 * @param 	reference
	 * 			The reference of the subdungeon to check to overlap the given orientation.
	 * @return	Return false if the given reference is lain outside the given orientation Or if
	 * 			the given orientation plus the maximum coordinates of the subdungeon at the given orientation
	 * 			is smaller than the given reference.
	 * 			| result == !reference.isOutside(orientation)
	 *			|	&& reference.add(getSubDungeonWithReference(reference).getMaxCoordinates())
	 *			|	.isLargerThanOrEqualTo(orientation)
	 * @throws 	IllegalArgumentException
	 * 			The given orientation and reference must be effective and this composite
	 * 			dungeon must have a subdungeon with the given reference.
	 * 			| orientation == null || reference == null || !hasSubDungeonWithReference(reference) 
	 */
	public boolean overlapsWith(Orientation orientation, Orientation reference)
	throws IllegalArgumentException{
		if(orientation == null || reference == null || !hasDirectSubDungeonWithReference(reference))
			throw new IllegalArgumentException("The given orientation and reference must be effective and this composite " +
					"dungeon must have a subdungeon with the given reference!");
		return(!reference.isOutside(orientation)
				&& reference.add(getSubDungeonWithReference(reference).getMaxCoordinates())
				.isLargerThanOrEqualTo(orientation));
	}

	/**
	 * Check whether there are overlaps with the dungeon at the given orientation and the other 
	 * dungeons of the given subDungeons.
	 * 
	 * @param	orientation
	 * 			The orientation from which the subDungeon must be checked from overlaps.
	 * @param 	subDungeons
	 * 			the map of subDungeons in which the subDungeon at the given orientation must be checked from overlaps.
	 * @return	Return false if one of the subDungeons (except the subDungeon to be checked itself) has a
	 * 			referenceOrientation larger than the referenceOrientation of the checkedDungeon, and smaller than
	 * 			the maxCoordinates of the checkedDungeon in the compositDungeon.
	 * 			|take checkedSubDungeon = subDungeons.get(orientation)
	 * 			|for each referenceOrientation in subDungeons.keySet()
	 * 			|	if(!referenceOrientation.isOutside(orientation.add(checkedSubDungeon.getMaxCoordinates()))
	 *			|		&& referenceOrientation.add(
	 *			|			subDungeons.get(referenceOrientation).getMaxCoordinates())
	 *			|				.isLargerThan(orientation))
	 *			|	then result == false
	 */
	public boolean withoutOverlaps(Orientation orientation, 
			HashMap<Orientation, Dungeon<E>> subDungeons){
		//declareer de subDungeon die op de gegeven orientatie van de subdungeons map staat
		Dungeon<E> checkedSubDungeon = subDungeons.get(orientation);
		//verwijder de subDungeon uit de map van dungeons
		HashMap<Orientation, Dungeon<E>> otherSubDungeons = new HashMap<Orientation, Dungeon<E>>(subDungeons);
		otherSubDungeons.remove(orientation);
		//itereer over de map van dungeons, vraag van elke dungeon of zijn referentieOrientatie tussen de
		//referentieOrientatie en de maxOrientatie van de gecheckte subdungeon ligt, zo ja, return false
		for(Orientation referenceOrientation : otherSubDungeons.keySet()){
			if(!referenceOrientation.isOutside(orientation.add(checkedSubDungeon.getMaxCoordinates()))
					&& referenceOrientation.add(
							otherSubDungeons.get(referenceOrientation).getMaxCoordinates())
						.isLargerThanOrEqualTo(orientation))
				return false;
		}
		return true;	
	}

	/**
	 * Check whether the subDungeon at the given orientation is a valid dungeon at the given orientation
	 * in the given map of subdungeons.
	 * 
	 * @param	orientation
	 * 			The orientation from which the subDungeon must be checked.
	 * @param 	subDungeons
	 * 			the map of subDungeons in which the subDungeon at the given orientation must be checked.
	 * @return	return false if there is no subDungeon in the given subDungeons at the given orientation.
	 * 			|if(subDungeons.get(orientation) == null)
	 *			|	then result == false
	 * @return	return false if there are overlaps with the dungeon at the given orientation and the other 
	 *			dungeons of the given subDungeons.
	 *			|if(!withoutOverlaps(orientation, subDungeons))
	 *			| 	then result == false
	 */
	public boolean isValidDungeonAt(Orientation orientation, 
			HashMap<Orientation, Dungeon<E>> subDungeons) {
		//in de gegeven subdungeons moet op de gegeven orientatie een subdungeon staan
		if(subDungeons.containsKey(orientation) && subDungeons.get(orientation) == null)
			return false;
		//deze subdungeon mag niet overlappen met een andere subdungeon
		if(!withoutOverlaps(orientation, subDungeons))
			return false;
		
		//deze subdungeon mag niet buiten deze composite dungeon vallen!
		if(!isInsideDungeon(orientation, subDungeons.get(orientation)))
			return false;
		return true;
	}

	/**
	 * Check whether the given map is a valid map of subdungeons for this square.
	 * 
	 * @param 	subDungeons 
	 * 			the subDungeons to check
	 * @return	return false if the given map is not effective
	 * 			|if(subDungeons == null)
	 * 			| then result == false
	 * @return	return false if there is one subDungeon which is not valid in the given map of subDungeons
	 * 			|for each orientation : subDungeons.keySet()
	 * 			| if(!isValidDungeonAt(orientation, subDungeons))
	 * 			| then result == false
	 */
	public boolean areValidSubDungeons(HashMap<Orientation, Dungeon<E>> subDungeons) {
		if(subDungeons == null)
			return false;
		for(Orientation orientation : subDungeons.keySet()){
			if(!isValidDungeonAt(orientation, subDungeons))
				return false;
		}
		return true;
	}
	
	/**
	 * Check whether the map of subDungeons is a proper map of subDungeons for this compositeDungeon
	 * 
	 * @return	if the map of subDungeons of this compositeDungeon is not effective return false
	 * 			|if(!areValidSubDungeons(getSubDungeons()))
	 * 			| then result == false
	 * @return	return false if some of the subDungeons in the map of subDungeons of this compositeDungeon	
	 * 			does not yet reference to this compositeDungeon as its motherDungeon
	 * 			|if for some orientation :getSubDungeons().keySet())
	 * 			|	!getSubDungeons().get(orientation).getMotherDungeon().equals(this))
	 * 			| then result == false
	 */
	public boolean hasProperSubDungeons(){
		if(!areValidSubDungeons(getSubDungeons()))
			return false;
		for(Orientation orientation :getSubDungeons().keySet()){
			if(!getSubDungeons().get(orientation).getMotherDungeon().equals(this))
				return false;
		}
		return true;
	}
	
	/**
	 * Check whether the given subdungeon can be inserted into the map of subdungeons of this
	 * compositeDungeon at the given orientation.
	 * 
	 * @param	orientation
	 * 			The orientation at which the subDungeon wants to insert
	 * @param 	subDungeon
	 * 			the subDungeon to be checked.
	 * @return 	return false if the given subDungeon already has a motherDungeon.
	 * 			|if(subDungeon.getMotherDungeon() != null)
	 * 			| then result == false
	 * @return	return false if this composite dungeon has a subdungeon with the given
	 * 			reference orientation.	
	 * 			|if(hasSubDungeonWithReference(orientation)
	 * 			|	then (result == false)
	 * @return	return false if the given subDungeon is not a valid SubDungeon at the given
	 * 			orientation in the map
	 * 			|take 	newSubDungeons = getSubDungeons();
	 *			|		newSubDungeons.put(orientation, subDungeon);
	 * 			|if(!isValidDungeonAt(orientation, subDungeons))
	 * 			| then result == false
	 */
	public boolean canInsert(Orientation orientation, Dungeon<E> subDungeon){
		//de subdungeon mag niet reeds een moeder hebben
		if(subDungeon.getMotherDungeon() != null)
			return false;
		
		//Er mag niet reeds een subdungeon zijn met dezelfde referentie.
		if(hasDirectSubDungeonWithReference(orientation))
			return false;
		
		//de map van dungeons waar de gegeven dungeon in wordt toegevoegd moet geldig zijn
		HashMap<Orientation, Dungeon<E>> newSubDungeons = 
			getSubDungeons();
		newSubDungeons.put(orientation, subDungeon);
		
		if(!isValidDungeonAt(orientation, newSubDungeons))
			return false;
		
		return true;
	}

	/**
	 *  Remove subdungeon from this composite dungeon.
	 * 
	 * @param	orientation
	 * 			An orientation of the subdungeon to remove.
	 * @post	The new composite dungeon does not references the given dungeon.
	 * 			| ! new.hasAsSubDungeon(subDungeon)
	 * @post	The given subDungeon does not references this composite dungeon as its mother dungeon.
	 * 			| (new subDungeon).getMotherDungeon() != this
	 * @post	The new composite dungeon does not reference any square of the given dungeon.
	 * 			| ! for some square of getSubDungeonAt(orientation) :
	 * 			|		new.getSquares().containsValue(square)
	 */
	protected void removeSubDungeonAt(Orientation orientation) {
		//checken of er wel een dungeon is op deze orientatie
		if(hasDirectSubDungeonAt(orientation)) {
			Orientation reference = getReferenceOfSubDungeonAt(orientation);
			Dungeon<E> subDungeon = getSubDungeonWithReference(reference); 
			//subudngeon verwijderen uit veld subdungeons
			subDungeons.remove(reference);
			subDungeon.setMotherDungeon(null);
			
			//alle vakjes van verwijderde dungeon afgaan
			for(Orientation squareOrientation : subDungeon.getSquares().keySet()){
				//vakjes verwijderen uit veld in deze composite dungeon
				squares.remove(squareOrientation.add(reference));
				//checken of het een edgevakje is dat verwijderd wordt.
				if(subDungeon.isEdgeOrientation(squareOrientation)){
					//voor alle buren de border ontkoppelen
					for(Orientation edgeOut : squareOrientation.getOutsideEdges(new Orientation(0,0,0), subDungeon.getMaxCoordinates())){
						if(hasSquareAt(edgeOut.add(reference))){
							E inside = subDungeon.getSquareAt(squareOrientation);
							E outside = getSquareAt(edgeOut.add(reference));
							Border border = inside.getSharedBorder(outside);
							border.split();
						}
					}
				}
			}
		}
	}

	/**
	 * Set a subdungeon to the map of subdungeons of this square at the given orientation.
	 * 
	 * @param 	subDungeon 
	 * 			the subDungeon to add to the map of subdungeons of this compositeDungeon.
	 * @post	The subdungeon will be add to the map of subdungeons of this square.
	 * 			|this.subDungeons == subDungeons
	 */
	protected void setSubDungeon(Orientation reference, Dungeon<E> subDungeon) {
		//zet alle vakjes in het veld vakjes
		for(Orientation orientation : subDungeon.getSquares().keySet())
			this.squares.put(orientation.add(reference), subDungeon.getSquares().get(orientation));
		//itereer over alle edgeSquares van de gegeven subDungeon en kijk of er op de orientaties daarlangs vakjes liggen
		Map<Orientation,E> edgeSquares = subDungeon.getEdgeSquares();
		for(Orientation orientationEdge : edgeSquares.keySet()){
			Orientation rootEdge = orientationEdge.add(reference);
			for(Orientation outsideEdgeOrientation : 
					rootEdge.getOutsideEdges(reference, subDungeon.getMaxCoordinates().add(reference))){
				if(this.hasSquareAt(outsideEdgeOrientation))
					setSharedBorder(rootEdge, outsideEdgeOrientation);
			}
		}
		if(getMotherDungeon() != null) {
			Orientation referenceInMother = getMotherDungeon().getReferenceOf(this);
			referenceInMother = referenceInMother.add(reference);
			getMotherDungeon().setSubDungeon(referenceInMother, subDungeon);
		}
	}
	
	/**
	 * Insert the given subDungeon in the map of subDungeons of this square.
	 * 
	 * @param	orientation
	 * 			The orientation at which the subDungeon wants to insert
	 * @param 	subDungeon
	 * 			the subDungeon to be inserted
	 * @throws	IllegalStateException
	 * 			|if(this.isTerminated())
	 * @throws	IllegalArgumentException
	 * 			|if(! canInsert(orientation, subDungeon))
	 * @effect	add the given subDungeon to the map of subDungeons of this compositeDungeon
	 * 			|setSubDungeon(orientation, subDungeon)
	 * @effect	set the mother dungeon of subDungeon to this compositeDungeon
	 * 			|subDungeon.setMotherDungeon(this)
	 */
	public void insertSubDungeon(Orientation orientation, Dungeon<E> subDungeon)	
		throws IllegalStateException, IllegalArgumentException{
	
		if (this.isTerminated())
			throw new IllegalStateException("Terminated motherDungeon!");
		
		if (! canInsert(orientation, subDungeon))
			throw new IllegalArgumentException
			("The given dungeon can not be attached to this dungeon in the given orientation!");
		
		setSubDungeon(orientation, subDungeon);
		subDungeons.put(orientation, subDungeon);
		subDungeon.setMotherDungeon(this);

	}

	/**
	 * Variable registering the subdungeons of this compositeDungeon
	 */
	private HashMap<Orientation, Dungeon<E>> subDungeons =
		new HashMap<Orientation, Dungeon<E>>();
	
	/**
	 * Return an iterator returning all the squares in this 
	 * composite dungeon one by one.
	 */
	@Override
	public Iterator<E> iterator() {
		return new Iterator<E>() {
			
			@Override
			public boolean hasNext() {
				if(currentIterator == null) {
					if(subDungeonIterator.hasNext()) {
						currentSubDungeon = subDungeonIterator.next();
						currentIterator = currentSubDungeon.iterator();
					}
					else
						return false;
				}
				if(currentIterator.hasNext())
					return true;
				if(!subDungeonIterator.hasNext())
					return false;
				currentSubDungeon = subDungeonIterator.next(); 
				currentIterator = currentSubDungeon.iterator();
				return hasNext();
			}

			@Override
			public E next() throws NoSuchElementException {
				if (!hasNext())
					throw new NoSuchElementException();
					return currentIterator.next();
			}

			@Override
			public void remove() throws UnsupportedOperationException {
				throw new UnsupportedOperationException();
			}
			
			private Iterator<Dungeon<E>> subDungeonIterator = getSubDungeons().values().iterator();
			private Dungeon<E> currentSubDungeon;
			private Iterator<E> currentIterator;

		};
	}
}
